/**
 * Original source:
 * http://www.gotdotnet.com/community/usersamples/details.aspx?sampleguid=6699aa41-7b59-40ec-a30d-da3afe63bd05
 */

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;

namespace Weys.Lib
{
    /// <summary>
    /// The fastbitmap enumerator
    /// </summary>
    public unsafe class FastBitmapEnumerator : IDisposable
    {
        int x;
        int y;
        FastBitmap fastBitmap;
        PixelData* pCurrentPixel;
        bool locked;
        /// <summary>
        /// The fastbitmap enumerator
        /// </summary>
        /// <param name="fastBitmap"></param>

        public FastBitmapEnumerator(FastBitmap fastBitmap)
        {
            fastBitmap.LockBitmap();
            locked = true;
            this.fastBitmap = fastBitmap;
            x = -1;
            y = 0;
            pCurrentPixel = fastBitmap[x, y];
        }

        /// <summary>
        /// Dispose the fastbitmap
        /// </summary>
        public void Dispose()
        {
            if (locked)
            {
                fastBitmap.UnlockBitmap();
            }
        }

        /// <summary>
        /// Move to next pixel
        /// </summary>
        /// <returns></returns>
        public bool MoveNext()
        {
            x++;
            pCurrentPixel++;
            if (x == fastBitmap.Size.X)
            {
                y++;
                if (y == fastBitmap.Size.Y)
                {
                    return false;
                }
                else
                {
                    x = 0;
                    pCurrentPixel = fastBitmap[0, y];
                    //Debug.WriteLine(String.Format("{0}", pCurrentPixel - fastBitmap[0, 0]));
                }
            }
            return true;
        }
        /// <summary>
        /// the current pixeldata 
        /// </summary>
        public PixelData* Current
        {
            get
            {
                return pCurrentPixel;
            }
        }


    }

    /// <summary>
    /// A bitmap class that allows fast x, y access 
    /// </summary>
    public unsafe class FastBitmap
    {
        Bitmap bitmap;

        // three elements used for MakeGreyUnsafe
        int width;
        int height;
        BitmapData bitmapData = null;
        Byte* pBase = null;
        PixelData* pCurrentPixel = null;
        int xLocation;
        int yLocation;
        Point size;
        public bool locked = false;

        /// <summary>
        /// Create an instance from an existing bitmap
        /// </summary>
        /// <param name="bitmap">The bitmap</param>
        public FastBitmap(Bitmap bitmap)
        {
            this.bitmap = bitmap;

            GraphicsUnit unit = GraphicsUnit.Pixel;
            RectangleF bounds = bitmap.GetBounds(ref unit);

            size = new Point((int)bounds.Width, (int)bounds.Height);
        }

        /// <summary>
        /// Save the bitmap to a file
        /// </summary>
        /// <param name="filename">Filename to save the bitmap to</param>
        public void Save(string filename)
        {
            bitmap.Save(filename, ImageFormat.Jpeg);
        }
        /// <summary>
        /// Dispose the bitmap
        /// </summary>
        public void Dispose()
        {
            bitmap.Dispose();
        }

        /// <summary>
        /// Size of the bitmap in pixels
        /// </summary>
        public Point Size
        {
            get
            {
                return size;
            }
        }

        /// <summary>
        /// The Bitmap object wrapped by this instance
        /// </summary>
        public Bitmap Bitmap
        {
            get
            {
                return (bitmap);
            }
        }

        /// <summary>
        /// Start at the beginning of the bitmap
        /// </summary>
        public void InitCurrentPixel()
        {
            LockBitmap();
            //if (pBase == null)
            //{
            //		throw new InvalidOperationException("Bitmap must be locked before calling InitCurrentPixel()");
            //		}
            pCurrentPixel = (PixelData*)pBase;
        }

        /// <summary>
        /// Return the next pixel
        /// </summary>
        /// <returns>The next pixel, or null if done</returns>
        public PixelData* GetNextPixel()
        {
            PixelData* pReturnPixel = pCurrentPixel;
            if (xLocation == size.X)
            {
                xLocation = 0;
                yLocation++;
                if (yLocation == size.Y)
                {
                    UnlockBitmap();
                    return null;
                }
                else
                {
                    pCurrentPixel = this[0, yLocation];
                }
            }
            else
            {
                xLocation++;
                pCurrentPixel++;
            }
            return pReturnPixel;
        }

        /// <summary>
        /// Get the pixel data at a specific x and y location
        /// </summary>
        public PixelData* this[int x, int y]
        {
            get
            {
                return (PixelData*)(pBase + y * width + x * sizeof(PixelData));
            }
        }
        /// <summary>
        /// Get the Enumerator
        /// </summary>
        /// <returns></returns>
        public FastBitmapEnumerator GetEnumerator()
        {
            return new FastBitmapEnumerator(this);
        }

        /// <summary>
        /// Set Pixel in color <paramref name="c"/> at (<paramref name="x"/>,<paramref name="y"/>)
        /// </summary>
        /// <param name="x">the horizontal coordinate</param>
        /// <param name="y">the vertical coordinate</param>
        /// <param name="c">the color to be set</param>
        public void SetPixel(int x, int y, Color c)
        {
            PixelData* pPixel = this[x, y];
            byte value_R = (byte)c.R;
            byte value_G = (byte)c.G;
            byte value_B = (byte)c.B;
            byte value_A = (byte)c.A;
            pPixel->red = value_R;
            pPixel->green = value_G;
            pPixel->blue = value_B;
            pPixel->alpha = value_A;
        }

        /// <summary>
        /// Merge the pixel (<paramref name="x"/>,<paramref name="y"/>) value in current fastbitmap with another fastbitmap <paramref name="bmp_m"/>
        /// </summary>
        /// <param name="x">the horizontal coordinate</param>
        /// <param name="y">the vertical coordinate</param>
        /// <param name="bmp_m">the fastbitmap to be merged with</param>
        public void MergeWith(int x, int y, FastBitmap bmp_m)
        {
            PixelData* pPixel = this[x, y];
            PixelData* pPixel_m = bmp_m[x, y];
            int v = 3;
            pPixel->alpha = 255;//(byte)((int)(pPixel->alpha * v + pPixel_m->alpha) / (1 + v));
            pPixel->red = (byte)((int)(pPixel->red * v + pPixel_m->red) / (1 + v));
            pPixel->green = (byte)((int)(pPixel->green * v + pPixel_m->green) / (1 + v));
            pPixel->blue = (byte)((int)(pPixel->blue * v + pPixel_m->blue) / (1 + v));
        }

        /// <summary>
        /// Set Pixel at (<paramref name="x"/>,<paramref name="y"/>) in the color of the pixel at (<paramref name="x_c"/>,<paramref name="y_c"/>) in another fastbitmap <paramref name="bmp_c"/>       
        /// </summary>
        /// <param name="x">the horizontal coordinate</param>
        /// <param name="y">the vertical coordinate</param>
        /// <param name="bmp_c"></param>
        /// <param name="x_c"></param>
        /// <param name="y_c"></param>
        public void SetPixel(int x, int y, FastBitmap bmp_c, int x_c, int y_c)
        {
            PixelData* pPixel = this[x, y];
            PixelData* pPixel_c = bmp_c[x_c, y_c];
            pPixel->red = pPixel_c->red;
            pPixel->green = pPixel_c->green;
            pPixel->blue = pPixel_c->blue;
            pPixel->alpha = pPixel_c->alpha;
        }

        /// <summary>
        /// Judge wether the color at (<paramref name="x"/>,<paramref name="y"/>) equals to color <paramref name="c"/>
        /// </summary>
        /// <param name="x">the horizontal coordinate</param>
        /// <param name="y">the vertical coordinate</param>
        /// <param name="c">the color to be compared with</param>
        /// <returns></returns>
        public bool ColorEqual(int x, int y, Color c)
        {
            return
                (this[x,y]->alpha == (byte)c.A &&
                this[x,y]->red == (byte)c.R &&
                this[x,y]->green == (byte)c.G &&
                this[x,y]->blue == (byte)c.B);


        }

        /// <summary>
        /// Lock the bitmap. 
        /// </summary>
        public void LockBitmap()
        {
            GraphicsUnit unit = GraphicsUnit.Pixel;
            RectangleF boundsF = bitmap.GetBounds(ref unit);
            Rectangle bounds = new Rectangle((int)boundsF.X,
                (int)boundsF.Y,
                (int)boundsF.Width,
                (int)boundsF.Height);

            // Figure out the number of bytes in a row
            // This is rounded up to be a multiple of 4
            // bytes, since a scan line in an image must always be a multiple of 4 bytes
            // in length. 
            width = (int)boundsF.Width * sizeof(PixelData);
            height = (int)boundsF.Height;
            if (width % 4 != 0)
            {
                width = 4 * (width / 4 + 1);
            }

            bitmapData =
                bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppPArgb);

            pBase = (Byte*)bitmapData.Scan0.ToPointer();
            locked = true;
        }

        /// <summary>
        /// Unlock the bitmap
        /// </summary>
        public void UnlockBitmap()
        {
            bitmap.UnlockBits(bitmapData);
            bitmapData = null;
            pBase = null;
            locked = false;
        }

        /// <summary>
        /// duplicate a fastbitmap
        /// </summary>
        /// <returns>duplicated fastbitmap</returns>
        public FastBitmap Duplicate()
        {
            bool bitmapLock = this.locked;
            if (bitmapLock)
                this.UnlockBitmap();

            Bitmap bm = new Bitmap(this.Bitmap);
            FastBitmap fbm = new FastBitmap(bm);

            if (bitmapLock)
                this.UnlockBitmap();
            else
                this.LockBitmap();
            return fbm;
        }
    }
}
